import { Observable } from 'rxjs';
import ResizeObserver from 'resize-observer-polyfill';

export interface exTableProp {
    prop: string;
    canEdit?: boolean;
    type?: 'number' | 'string';
}

export interface itemRectSize {
    left: number;
    right: number;
    top: number;
    bottom: number;
    width: number;
    height: number;
}

export interface ValueChangeData<T = any> { data: T, head: exTableProp }

export type exTablePropArr = Array<exTableProp | string>;

export function getElePosition(ele: HTMLTableCellElement): Position {
    return {
        row: Number(ele.parentElement.dataset['row']),
        col: ele.cellIndex
    };
}

export function isEqualRect(a: Position, b: Position): boolean {
    return a.row === b.row && a.col === b.col;
}

export interface Position {
    row: number;
    col: number;
}

export interface AreaRect {
    start: Position;
    end: Position;
}

// 矫正area中的start和end元素
export function rectifyArea(startRect: Position, endRect: Position): AreaRect {
    let {row: startIndex, col: startProp} = startRect;
    let {row: endIndex, col: endProp} = endRect;
    if (startIndex > endIndex) {
        [startIndex, endIndex] = [endIndex, startIndex];
    }
    if (startProp > endProp) {
        [startProp, endProp] = [endProp, startProp];
    }
    return {
        start: {
            row: startIndex,
            col: startProp
        },
        end: {
            row: endIndex,
            col: endProp
        }
    };
}

export function pointInArea(rect: AreaRect, point: Position): Boolean {
    return rect.start.row <= point.row && rect.end.row >= point.row && rect.start.col <= point.col && rect.end.col >= point.col;
}

export function rectifyAreaByPoint(...pointArr: Position[]): AreaRect {
    let rowList = [];
    let colList = [];
    pointArr.forEach(item => {
        rowList.push(item.row);
        colList.push(item.col);
    });
    rowList.sort((a, b) => a - b);
    colList.sort((a, b) => a - b);
    return {
        start: {
            row: rowList[0],
            col: colList[0]
        },
        end: {
            row: rowList[rowList.length - 1],
            col: colList[colList.length - 1]
        }
    };
}

// 两个区域的差集
export function differenceSet(minArea: AreaRect, bigArea: AreaRect): AreaRect {
    let currRect = {start: [bigArea.start.row, bigArea.start.col], end: [bigArea.end.row, bigArea.end.col]};
    let lastRect = {start: [minArea.start.row, minArea.start.col], end: [minArea.end.row, minArea.end.col]};
    let rect = {start: [], end: []};
    if (lastRect.start.join() == currRect.start.join() && lastRect.end.join('') == currRect.end.join('')) {
        return null;
    }
    if (lastRect.start.join() == currRect.start.join()) {
        rect.end = currRect.end;
        rect.start = currRect.end[1] == lastRect.end[1] ? [lastRect.end[0] + 1, lastRect.start[1]]:[lastRect.start[0], lastRect.end[1] + 1];
    } else if (lastRect.end.join('') == currRect.end.join('')) {
        rect.start = currRect.start;
        rect.end = currRect.start[0] == lastRect.start[0] ? [lastRect.end[0], lastRect.start[1] - 1]:[lastRect.start[0] - 1, lastRect.end[1]];
    }
    return {
        start: {
            row: rect.start[0],
            col: rect.start[1],
        },
        end: {
            row: rect.end[0],
            col: rect.end[1],
        }
    };
}

const strNumRegex = /^(.*?)(\d*\.?\d+)(\D*)$/;

export function isStrNum(str: string | number): { value: number; has: boolean } {
    const res = {has: false, value: 0};
    const match = str.toString().match(strNumRegex);
    if (match) {
        res.value = Number(match[2]);
        res.has = true;
    }
    return res;
}

export interface plusValue {
    value: any;
    reserve?: boolean;
}

export interface plusStep {
    val: number;
    len: number;
}

// 字符的递增
export function plusStrNum(data: string | number, step: plusStep, stepFactor: 1 | -1): plusValue {
    let reserve = false;
    if (typeof data !== 'number') {
        data = data.replace(strNumRegex, (match, p1, p2, p3) => {
            p2 = Number(p2) + step.val * stepFactor;
            if (p2 <= 0) {
                reserve = true;
                p2 = Math.abs(p2).toFixed(step.len);
            }
            return p1 + p2 + p3;
        });
    } else {
        data = data + step.val * stepFactor;
        reserve = data <= 0;
        data = Number(data.toFixed(step.len));
    }

    return {value: data, reserve};
}

export function replaceStrNum(data, value = 0) {
    return data.replace(strNumRegex, (match, p1, p2, p3) => {
        return p1 + value + p3;
    });
}

export interface dataRule {
    step?: plusStep;
    isStrNum?: boolean;
    strNumValue?: number;
    value: any;
    canDive: boolean;
}

export interface rowDataRule {
    rules: dataRule[];
    arithmetic: boolean;
    step?: plusStep;
    leftValue?: number;
    rightValue?: number;
    leftSource?: number | string;
    rightSource?: number | string;
}

// 获取数据的规则，目前两种等差数列和数字，不连续的按单个算
export function analysisDataRule(data: any[][], rowRule = true): rowDataRule[] {
    let transFromData = data;
    if (!rowRule) {
        transFromData = Array.from({length: data[0].length}, () => ([]));
        data.forEach((item, rowIndex) => item.forEach((val, colIndex) => {
            transFromData[colIndex][rowIndex] = val;
        }));
    }
    return transFromData.map((item) => {
        let allIsNum = true, allIsStrNum = true, lastStrNum = '';
        let rule: dataRule[] = [];
        item.forEach((val, index) => {
            let isNum = false, strNum = {value: val, has: false};
            if (val !== null && val !== undefined && (typeof val === 'string' || typeof val === 'number')) {
                isNum = isNumber(val);
                strNum = isStrNum(val);
            }
            allIsNum &&= isNum;
            if (!isNum && strNum.has && allIsStrNum) {
                let currStrNum = replaceStrNum(val);
                if (index !== 0) {
                    allIsStrNum &&= lastStrNum === currStrNum;
                }
                lastStrNum = currStrNum;
            } else {
                allIsStrNum = false;
            }
            if (isNum || strNum.has) {
                rule.push({
                    value: val,
                    canDive: true,
                    isStrNum: !isNum,
                    strNumValue: isNum ? val:strNum.value,
                    step: getStep(val)
                });
            } else {
                rule.push({value: val, canDive: false});
            }
        });
        let rowDataRule: rowDataRule = {
            rules: rule,
            arithmetic: false
        };
        if (allIsNum || allIsStrNum) {
            Object.assign(rowDataRule, arithmeticNums(rule));
        }
        return rowDataRule;
    });
}

export function isNumber(val): boolean {
    return typeof val === 'number';
}

export function getDecimalsLen(value) {
    return value.toString().match(/\d*\.(\d+)/);
}

export function getStep(value: number): { val: number; len: number } {
    const match = getDecimalsLen(value);
    if (match) {
        return {val: 1 / Math.pow(10, match.length), len: match.length};
    }
    return {val: 1, len: 0};
}

export function arithmeticNums(rules: dataRule[]) {
    if (rules.length === 1) {
        return {
            step: rules[0].step,
            arithmetic: true,
            leftSource: rules[0].value,
            rightSource: rules[0].value
        };
    }
    let isAri = true;
    let s = rules[1].strNumValue - rules[0].strNumValue;
    // 是否为等比数列
    for (let i = 2; i < rules.length; i++) {
        // TODO  小数位数可能有bug
        isAri = s === rules[i].strNumValue - rules[i - 1].strNumValue;
        if (!isAri) {
            break;
        }
    }
    if (isAri) {
        let len = Math.max.apply(null, rules.map(r => getDecimalsLen(r.strNumValue)));
        s = Number(s.toFixed(len));
        return {
            step: {val: s, len},
            arithmetic: true,
            leftSource: rules[0].value,
            rightSource: rules[rules.length - 1].value
        };
    }
    return {
        arithmetic: false
    };
}

export function resizeListener(element: HTMLElement): Observable<DOMRect> {
    return new Observable(subscriber => {
        const ro = new ResizeObserver((entries, observer) => {
            // @ts-ignore
            subscriber.next(entries[0].contentRect);
        });
        ro.observe(element);
        return {
            unsubscribe() {
                ro.unobserve(element);
                ro.disconnect();
            }
        };
    });
}

export function getTdElement(event: MouseEvent): HTMLTableCellElement {
    // @ts-ignore
    let elementPath: HTMLElement[] = event.composedPath();
    return elementPath.find(ele =>
        ele.tagName === 'TD'
        // 排除固定列
        // && !ele.classList.contains('ant-table-cell-fix-left')
        // && !ele.classList.contains('ant-table-cell-fix-right')
        // && !ele.classList.contains('ex-hidden')
        && !ele.classList.contains('nz-disable-td')
    ) as HTMLTableCellElement;
}

export function stopPropagation(event: Event) {
    event.stopPropagation();
}

export function scrollSpeed(distance: number, dis: number = 80) {
    return .2 * Math.pow(distance + dis, 2);
}

export interface BeyondArea {
    startRow: number;
    startCol: number;
    data: any[][];
    lastData?: any,
    startIndex: number;
}

export interface StyleObject {
    [prop: string]: any
}

const styleUnitMap: StyleObject = new Proxy({
    width: 'px',
    height: 'px',
    top: 'px',
    left: 'px',
    right: 'px',
    bottom: 'px',
    opacity: 0,
}, {
    get(target: StyleObject, p: string): any {
        return target[p] ?? ''
    }
})

export function getStyleSheet(style?: { [prop: string]: any }) {
    if (!style) {
        return 'display: none'
    }
    return Object.keys(style).reduce((a, b) => a + b + ':' + (style[b] + styleUnitMap[b]) + ';', 'display: block;')
}

export function transFormValueType(value, config: exTableProp){
    switch (config.type) {
        case 'number':
            return Number(value) || 0;
        default:
            return value;
    }
}



export interface EditPermission {
    moveDown: boolean;
    moveUp: boolean;
    add: boolean;
    addChild: boolean;
    insertUp: boolean;
    insertDown: boolean;
    upgradeUp: boolean;
    upgradeDown: boolean;
    del: boolean
}
export type EditPermissionType = keyof EditPermission;
export function listToTreeEx<T = any>(data: T[], config?: listToTreeOption<T>): T[] {
    config = Object.assign({children: 'children', parent: 'ParentGuid', id: 'Guid'}, config);
    const cProp = config.children;
    const cacheChildren: { [prop: string]: T[] } = {};
    const result: T[] = [];
    const cacheObj: { [prop: string]: T } = {};
    for (let item of data) {
        if (!item) {
            continue;
        }
        item[cProp] = [];
        const id = item[config.id];
        config.callBack && config.callBack(item);
        cacheObj[id] = item;
        const parentId = item[config.parent];
        if (!parentId) {
            result.push(item);
        } else if (cacheObj[parentId]) {
            cacheObj[parentId][cProp].push(item);
        } else {
            cacheChildren[parentId] ??= [];
            cacheChildren[parentId].push(item);
        }
    }
    Object.keys(cacheChildren).forEach(id => {
        cacheObj[id] && cacheObj[id][cProp].unshift(...cacheChildren[id]);
    });
    return result;
}

export interface TreeHelperOption<T = any> {
    children?: string;
    call?: (item: T, option: {
                parent: T,
                deep: number,
                brother: T[],
                colLen: number,
                zIndexArr: number[]
            }
    ) => void;
}

export interface listToTreeOption<T> {
    children?: string;
    id?: string;
    parent?: string;
    callBack?: (data: T) => void;
}


export function treeHelper<T = any>(data: T[], option: TreeHelperOption<T>): number {
    const {children: childrenProp, call}: TreeHelperOption<T> = Object.assign({
        children: 'children',
        call: (item, {deep}) => (item.level = deep),
    }, option || {});
    let deepSum = 0;
    // const result = [];
    const fun = (data, parent = null, deep = 0, index = 0, zIndexArr = []) => {
        if (Array.isArray(data) && data.length) {
            deepSum = Math.max(deepSum, deep);
            let colSum = data.reduce((col, item, i) => {
                zIndexArr.push(i);
                index++;
                let info = fun(item[childrenProp], item, deep + 1, index, [...zIndexArr]);
                index = info.index;
                call &&
                call(item, {
                    parent,
                    deep,
                    brother: data,
                    colLen: info.col,
                    zIndexArr: [...zIndexArr]
                });
                zIndexArr.pop();
                return (info.col === 0 ? 1:info.col) + col;
            }, 0);
            return {index, col: colSum};
        }
        return {index, col: 0};
    };
    fun(data);
    return deepSum;
}